#include "LoggerEngine.h"
#include <bitset>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <sstream>
#include <string>
#include <thread>
#include <type_traits>
#include <unistd.h>
#include <utility>
#include <vector>
#include "AsyncLogQueue.h"
#include "Common.h"
#include "Logger.h"
#include "LoggerFile.h"
#if __cplusplus >= 201402L
#    include "LoggerNew.h"
#endif

namespace xncc {
static constexpr std::size_t DEFAULT_LOG_LEN = 2048;
static constexpr size_t      DATA_TYEPE_LEN  = 1;
static constexpr uint32_t    HEADER_LEN      = sizeof(uint32_t);
static constexpr char        digitHex[]      = "0123456789abcdef";
static_assert(sizeof(digitHex) == 17, "wrong len of digitHex");
static constexpr int WAIT_MICRO_SECONDS = 100;

namespace {
    volatile bool logExit{false};

}  // namespace

LoggerEngine::LoggerEngine() : coreId_(-1)
{
    currentLogStr_.reserve(DEFAULT_LOG_LEN);
    queues.reserve(10);
}

LoggerEngine::~LoggerEngine()
{
    logExit = true;
    if (logThread_.joinable()) {
        logThread_.join();
    }
    for (auto* queue : queues) {
        ConsumeQueue(queue);
        delete queue;
    }
    flushFile();
}

static auto toHexStr(uintptr_t value) -> std::string
{
    uintptr_t         i = value;
    std::vector<char> buf;
    do {
        int idx = static_cast<int>(i % 16);
        i /= 16;
        buf.push_back(digitHex[idx]);
    } while (i != 0);
    return "0x" + std::string(buf.begin(), buf.end());
}

void LoggerEngine::start()
{
    logThread_ = std::thread(&LoggerEngine::run, this);
}

auto LoggerEngine::createAsyncQueue() -> AsyncLogQueue*
{
    auto* ptr = new AsyncLogQueue(1 << 21);

    std::lock_guard<std::mutex> lock(queues_mutex);
    queues.push_back(ptr);
    return ptr;
}

void LoggerEngine::addLogFile(std::shared_ptr<LoggerFile> file, uint16_t fileId, int coreId)
{
    userLogfiles[fileId] = std::move(file);
    coreId_              = coreId;
}

auto LoggerEngine::storeParamsInfo(const ParamStaticInfo* params) -> int32_t
{
    std::lock_guard<std::mutex> lock(fmtIdMutex);
    const auto                  fmtId = static_cast<int32_t>(paramStaticInfoVec.size());
    paramStaticInfoVec.push_back(params);
    return fmtId;
}

void LoggerEngine::run()
{
    if (coreId_ != -1) {
        cpu_set_t cpuSet;
        CPU_ZERO(&cpuSet);
        CPU_SET(coreId_, &cpuSet);
        pthread_t current = pthread_self();
        pthread_setaffinity_np(current, sizeof(cpu_set_t), &cpuSet);
    }
    tmpQueues_.reserve(10);
    deletedQueues_.reserve(10);
    while (!logExit) {
        {
            std::lock_guard<std::mutex> lock(queues_mutex);
            for (auto iter = queues.begin(); iter != queues.end();) {
                auto* ptr = *iter;
                if (UNLIKELY(ptr->canDestory())) {
                    deletedQueues_.push_back(ptr);
                    iter = queues.erase(iter);
                }
                else {
                    tmpQueues_.push_back(ptr);
                    ++iter;
                }
            }
        }
        for (auto* queue : deletedQueues_) {
            ConsumeQueue(queue);
            delete queue;
        }
        for (auto* queue : tmpQueues_) {
            ConsumeQueue(queue);
        }
        flushFile();
        auto empty = true;
        for (auto* queue : tmpQueues_) {
            if (!queue->empty()) {
                empty = false;
                break;
            }
        }
        if (empty) {
            std::this_thread::sleep_for(std::chrono::microseconds(WAIT_MICRO_SECONDS));
        }
        tmpQueues_.clear();
        deletedQueues_.clear();
    }
}

void LoggerEngine::ConsumeQueue(AsyncLogQueue* queue)
{
    while (true) {
        char* readBuf = queue->getReadBuffer(HEADER_LEN);
        if (readBuf != nullptr) {
            uint32_t bodyLen = *reinterpret_cast<uint32_t*>(readBuf);
            char*    dataBuf = queue->getReadBuffer(bodyLen);
            if (LIKELY(dataBuf)) {
                handleLogMsg(dataBuf + HEADER_LEN, bodyLen - HEADER_LEN);
                queue->finishRead();
                continue;
            }
        }
        break;
    }
}

void LoggerEngine::handleLogMsg(const char* msg, const uint32_t len)
{
    const auto hdrType = static_cast<LOG_PARAM_TYPE>(*(msg));
    msg += DATA_TYEPE_LEN;
    switch (hdrType) {
        case LOG_PARAM_TYPE::HEADER_V1_TYPE: {
            const auto* header = reinterpret_cast<const LogHeaderV1*>(msg);
            header->decode(currentLogStr_);
            const uint16_t loggerid = header->loggerId_;
            msg += xncc::LogHeaderV1::HEADER_LEN();
            decodeLogParams(msg, len - xncc::LogHeaderV1::HEADER_LEN() - DATA_TYEPE_LEN);
            userLogfiles[loggerid]->writeLogMsg(currentLogStr_);
            currentLogStr_.clear();
            break;
        }
        case LOG_PARAM_TYPE::HEADER_V2_TYPE: {
            const auto* header = reinterpret_cast<const LogHeaderV2*>(msg);
            header->decode(currentLogStr_);
            const uint16_t loggerid = header->loggerId_;
            msg += xncc::LogHeaderV2::HEADER_LEN();
            decodeLogMsgVesion(msg, len - xncc::LogHeaderV2::HEADER_LEN() - DATA_TYEPE_LEN, header->fmtId_);
            userLogfiles[loggerid]->writeLogMsg(currentLogStr_);
            currentLogStr_.clear();
            break;
        }
        case LOG_PARAM_TYPE::HEADER_V3_TYPE: {
            const auto*    header   = reinterpret_cast<const LogHeaderV3*>(msg);
            const uint16_t loggerid = header->loggerId_;
            msg += xncc::LogHeaderV3::HEADER_LEN();
            decodeLogMsgVesion(msg, len - xncc::LogHeaderV3::HEADER_LEN() - DATA_TYEPE_LEN, header->fmtId_);
            userLogfiles[loggerid]->writeLogMsg(currentLogStr_);
            currentLogStr_.clear();
            break;
        }
        case LOG_PARAM_TYPE::HEADER_V4_TYPE: {
            const auto*    header   = reinterpret_cast<const LogHeaderV4*>(msg);
            const uint16_t loggerid = header->loggerId_;
            msg += xncc::LogHeaderV4::HEADER_LEN();
            decodeLogParams(msg, len - xncc::LogHeaderV4::HEADER_LEN() - DATA_TYEPE_LEN);
            userLogfiles[loggerid]->writeLogMsg(currentLogStr_);
            currentLogStr_.clear();
            break;
        }
        case LOG_PARAM_TYPE::HEADER_V5_TYPE: {
            const auto*    header   = reinterpret_cast<const LogHeaderV5*>(msg);
            const uint16_t loggerid = header->loggerId_;
            msg += xncc::LogHeaderV5::HEADER_LEN();
            userLogfiles[loggerid]->writeBinary(msg, header->dataSize);
            break;
        }
        case LOG_PARAM_TYPE::HEADER_V6_TYPE: {
            const auto*    header   = reinterpret_cast<const LogHeaderV6*>(msg);
            const uint16_t loggerid = header->loggerId_;
            msg += xncc::LogHeaderV6::HEADER_LEN();
            userLogfiles[loggerid]->writeBinary(msg, len - xncc::LogHeaderV6::HEADER_LEN() - DATA_TYEPE_LEN);
            break;
        }
        default: {
            TRACE_DEBUG("header type error mybe memory offset error");
            break;
        }
    }
}

void LoggerEngine::flushFile()
{
    for (auto& ele : userLogfiles) {
        ele.second->flush();
    }
}
namespace {
    template <typename T>
    inline void
    convertField(std::string& ret, size_t& offset, const char* msg, LOG_PARAM_TYPE FLAG = LOG_PARAM_TYPE::DEC_FLAG_TYPE)
    {
        const T num = *reinterpret_cast<const T*>(msg + offset);
        if (std::is_same<T, double>::value) {
            ret += std::to_string(num);
            offset += sizeof(T);
            return;
        }
        if (FLAG == LOG_PARAM_TYPE::DEC_FLAG_TYPE) {
            ret += std::to_string(num);
        }
        else if (FLAG == LOG_PARAM_TYPE::HEX_FLAG_TYPE) {
            std::ostringstream oss;
            oss << "0x" << std::hex << num;
            ret += oss.str();
        }
        else if (FLAG == LOG_PARAM_TYPE::OCT_FLAG_TYPE) {
            std::ostringstream oss;
            oss << "0" << std::oct << num;
            ret += oss.str();
        }
        else if (FLAG == LOG_PARAM_TYPE::BIN_FLAG_TYPE) {
            ret += std::bitset<sizeof(T) * 8>(static_cast<unsigned long long>(num)).to_string();
        }
        else {
            ret += std::to_string(num);
        }

        offset += sizeof(T);
    }

}  // namespace

void LoggerEngine::decodeLogParams(const char* msg, size_t len)
{
    LOG_PARAM_TYPE flag   = LOG_PARAM_TYPE::NONE;
    size_t         offset = 0;
    while (offset < len) {
        auto pType = static_cast<LOG_PARAM_TYPE>(*(msg + offset));
        offset += DATA_TYEPE_LEN;
        switch (pType) {
            case LOG_PARAM_TYPE::DEC_FLAG_TYPE:
            case LOG_PARAM_TYPE::OCT_FLAG_TYPE:
            case LOG_PARAM_TYPE::HEX_FLAG_TYPE:
            case LOG_PARAM_TYPE::BIN_FLAG_TYPE: {
                flag = pType;
            } break;
            case LOG_PARAM_TYPE::SHORT_TYPE: {
                convertField<short>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::UNSIGED_SHORT_TYPE: {
                convertField<unsigned short>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::INT_TYPE: {
                convertField<int>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::UNSIGNED_INT_TYPE: {
                convertField<unsigned int>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::LONG_TYPE: {
                convertField<long>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::UNSIGNED_LONG_TYPE: {
                convertField<unsigned long>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::LONG_LONG_TYPE: {
                convertField<long long>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::UNSIGNED_LONG_LONG_TYPE: {
                convertField<unsigned long long>(currentLogStr_, offset, msg, flag);
            } break;
            case LOG_PARAM_TYPE::DOUBLE_TYPE: {
                convertField<double>(currentLogStr_, offset, msg);
            } break;
            case LOG_PARAM_TYPE::CHAR_PTR_TYPE: {
                const uint32_t strLen = *reinterpret_cast<const uint32_t*>(msg + offset);
                offset += sizeof(uint32_t);
                currentLogStr_ += std::string(msg + offset, strLen);
                offset += strLen;
            } break;
            case LOG_PARAM_TYPE::CONST_CHAR_PTR_TYPE: {
                const uintptr_t strAddr = *reinterpret_cast<const uintptr_t*>(msg + offset);
                const char*     str     = reinterpret_cast<const char*>(strAddr);
                currentLogStr_ += std::string(str);
                offset += sizeof(uintptr_t);
            } break;
            case LOG_PARAM_TYPE::PTR_TYPE: {
                const uintptr_t num = *reinterpret_cast<const uintptr_t*>(msg + offset);
                currentLogStr_ += toHexStr(num);
                offset += sizeof(uintptr_t);
            } break;
            case LOG_PARAM_TYPE::LOG_OBJ_TYPE: {
                const uint32_t entrySize = *reinterpret_cast<const uint32_t*>(msg + offset);
                offset += sizeof(uint32_t);
                const auto*  innerType = reinterpret_cast<const FunctionObjectInnerType*>(msg + offset);
                OBJ_LOG_FUNC logFunc   = *reinterpret_cast<const OBJ_LOG_FUNC*>(msg + offset + innerType->funcOffset);
                std::ostringstream oss;
                logFunc(reinterpret_cast<const void*>(msg + offset + innerType->objSize), oss);
                currentLogStr_ += oss.str();
                offset += entrySize;
            } break;
            case LOG_PARAM_TYPE::OVER_FLAG_TYPE: {
                currentLogStr_ += "\n";
                return;
            } break;
            default: {
                TRACE_DEBUG("header type error mybe memory offset error");
            } break;
        }
    }
}

void LoggerEngine::decodeLogMsgVesion(const char* msg, const size_t len, const int32_t fmtId)
{
#if __cplusplus >= 201402L
    size_t                 currentOffset = 0;
    const ParamStaticInfo* info          = nullptr;
    {
        std::lock_guard<std::mutex> lock(fmtIdMutex);
        info = paramStaticInfoVec.at(static_cast<std::size_t>(fmtId));
    }

    while (true) {
        if (info->posOrLen != 0) {
            currentLogStr_ += std::string(info->fmtStr, static_cast<std::size_t>(info->posOrLen));
        }
        if (info->type == PARAM_TYPE::FORMAT_STR_END) {
            break;
        }
        if (info->type == PARAM_TYPE::INTEGER) {
            convertField<long long>(currentLogStr_, currentOffset, msg);
        }
        else if (info->type == PARAM_TYPE::UNSIGNED_INTEGER) {
            convertField<unsigned long long>(currentLogStr_, currentOffset, msg);
        }
        else if (info->type == PARAM_TYPE::FLOAT) {
            convertField<double>(currentLogStr_, currentOffset, msg);
        }
        else if (info->type == PARAM_TYPE::CHAR) {
            currentLogStr_ += std::string(msg + currentOffset, 1);
            currentOffset += sizeof(char);
        }
        else if (info->type == PARAM_TYPE::PERCENT_SIGN) {
            currentLogStr_ += "%";
        }
        else if (info->type == PARAM_TYPE::PTR) {
            const uintptr_t num = *reinterpret_cast<const uintptr_t*>(msg + currentOffset);
            currentLogStr_ += toHexStr(num);
            currentOffset += sizeof(uintptr_t);
        }
        else if (info->type == PARAM_TYPE::HEX) {
            const unsigned long long num = *reinterpret_cast<const unsigned long long*>(msg + currentOffset);
            currentLogStr_ += toHexStr(num);
            currentOffset += sizeof(unsigned long long);
        }
        else if (info->type == PARAM_TYPE::STRING) {
            const uint32_t strLen = *reinterpret_cast<const uint32_t*>(msg + currentOffset);
            currentOffset += sizeof(uint32_t);
            currentLogStr_ += std::string(msg + currentOffset, strLen);
            currentOffset += strLen;
        }
        else if (info->type == PARAM_TYPE::CONST_STRING) {
            const uintptr_t strAddr = *reinterpret_cast<const uintptr_t*>(msg + currentOffset);
            currentLogStr_ += reinterpret_cast<const char*>(strAddr);
            currentOffset += sizeof(void*);
        }
        else if (info->type == PARAM_TYPE::FUNCTION) {
            const auto*  innerType = reinterpret_cast<const FunctionObjectInnerType*>(msg + currentOffset);
            OBJ_LOG_FUNC logFunc = *reinterpret_cast<const OBJ_LOG_FUNC*>(msg + currentOffset + innerType->funcOffset);
            currentOffset += static_cast<std::size_t>(innerType->objSize);
            void*              data = reinterpret_cast<void*>(const_cast<char*>(msg + currentOffset));
            std::ostringstream oss;
            logFunc(data, oss);
            currentLogStr_ += oss.str();
            currentOffset += static_cast<std::size_t>(innerType->dataSize);
        }
        else {
            printf("unkown fmtid\n");
        }
        info = info + 1;
    }
    currentLogStr_ += "\n";
#endif
}

}  // namespace xncc